Skip to content

Conversation

DarkGhostHunter
Copy link
Contributor

@DarkGhostHunter DarkGhostHunter commented Sep 20, 2025

What?

This is basically a refactoring of the Eloquent Casts that allows to expressively configure a casts, and standardize features like encryption, caching and mapping (for iterable casts like Collections or even Fluent).

This PR solves some problems for the built-in casts:

  • Cannot disable object caching. To do that, the developer must create its own cast.
  • AsFluent, AsHtmlString and AsUri are not encryptable.
  • Cannot use map or using on an AsArrayObject, while AsCollection does.

This is opt-in, meaning, there are no changes to the already included casts, but I believe the new way to declare casts should be preferred when possible when the developer requires the new features. Aka, this PR is non breaking.

How?

The way it works is by introducing an entry-point class called To, with static methods that return an object that configure the selected cast enabled by #56687.

Note

As is reserved by PHP and won't work, that's why I decanted to use To.

use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Database\Eloquent\Casts\To;

// Before
public function casts()
{
    return [
         'colors' => AsCollection::of(Color::class)   
    ];   
}

// After
public function casts()
{
    return [
         'colors' => To::collection()->of(Color::class)
    ];   
}

The static methods are:

Method Equivalent
To::fluent() AsFluent
To::htmlString() AsHtmlString
To::stringable() AsStringable
To::uri() AsUri
To::arrayObject() AsArrayObject
To::collection() AsCollection

For example, we can now fluently configure an ArrayObject to use a custom Array Object class and items or even disable caching for a Fluent object.

use Illuminate\Database\Eloquent\Casts\To;

// An Array Object using a custom class and custom item instances.
To::arrayObject()->using(MyCustomArrayObject::class)->of(Color::class);

// An HTML String instance that is not cached.
To::fluent()->withoutCaching();

// An encrypted collection that maps "KeyParser" using the `fromArray()` static method.
To::collection()->encrypted()->mappedFromArray(KeyParser::class);

To achieve all this, the To class instances two objects depending on what is being casted: ToIterable or ToString. Once these are set into the cast array, these will serialize into strings with the cast configuration.

A minor adjustment had to be made for the ArrayObject. Due to the serialize() method present in the original cast, the ArrayObjectCast had to be created. Aside from that, I believe this implementation is cleaner in the sense that one Cast handles iterable objects, while other does for stringable objects.

Finally, if the developer is using a custom class with using(), the class check is deferred until the cast is used, like currently is done.

Extensibility

The IterableCast can also be extended by developers since both mapping and final instantiation is done using mapItems() and makeIterableObject(), respectively.

class MyIterableCast extends IterableCast
{
    protected function mapItems($data)
    {
        // Instance each item here...
    }

    protected function makeIterableObject($data)
    {
        // Make the final iterable object here...
    }
}

class MyIterable implements Castable
{
    public function castUsing(array $arguments)
    {
        return new MyIterableCast();
    }
}

@Rizky92
Copy link
Contributor

Rizky92 commented Sep 26, 2025

To is too generic for me in terms of class name. Probably something like Cast::collection(Color::class) might be more expressive.

@DarkGhostHunter
Copy link
Contributor Author

To is too generic for me in terms of class name. Probably something like Cast::collection(Color::class) might be more expressive.

I wonder where do you use To too. Also, it's not like its in the global namespace.

Either way, it could be even a namespaced function called to, something I thought initially but I didn't want to do since I don't know @taylorotwell position about adding a namespaced function for this exclusive purpose.

use App\ValueObjects\Color;
use function Illuminate\Database\Eloquent\Cast\to;

public function casts()
{
    return [
        'colors' => to()->collection()->of(Color::class);
    ];
}

Not gonna lie, it looks more elegant to me. Also, it opens the ability to make to() accept an argument like a plain class and magically add encryption and and caching abilities. Behind the scenes, the CastAttributes will automatically check if the class is iterable (ArrayObject, Enumerable, iterable) and treat it from-and-to JSON.

use App\ValueObjects\Preferences;
use function Illuminate\Database\Eloquent\Cast\to;

public function casts()
{
    return [
        'preferences' => to(Preferences::class)->encrypted()->withoutCaching();
    ];
}

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants